package restdb

/* update plan: based on tdegrunt/mongodb-rest
*  	
*    GET /db?c=collection.name?cb=callback
*    GET /db?c=collection.name&id=id&cb=callback
*    GET /db?c=collection&cb=callback&q={field:"value"}
*    POST /db?c=collection DATA:new document
*    PUT /db?c=collection&id=id
*    DELETE /db?c=collection&id=id
 */

import (
	"code.google.com/p/cloud9lib/mongo"
	"code.google.com/p/cloud9lib/rest"
	"code.google.com/p/cloud9lib/syncd/httpd"
	"encoding/json"
	"io"
	"io/ioutil"
	"launchpad.net/mgo/bson"
	"log"
	"net/http"
	"errors"
	"strings"
)

func safe(c string) bool {

	return !strings.HasPrefix(c, "system.") && !strings.HasPrefix(c, "fs")
}

func AddCollections() {

	db := mongo.DB()

	collections, err := db.CollectionNames()

	if err != nil {
		log.Fatal("could not list DB collections: " + err.Error())
	}

	log.Println("creating feeds for MongoDB collections:")
	for i, c := range collections {
		if !safe(c) {
			continue
		}
		httpd.AddFeed(c)
		log.Println(i, c)
	}

	log.Println("done.")
}

func dbGETHandler(w http.ResponseWriter, req *http.Request) (int, error) {

	c := req.FormValue("c")
	cb := req.FormValue("cb")
	padding := req.FormValue("p")
	q := req.FormValue("q")
	id := req.FormValue("id")

	if c == "" {
		return 400, errors.New("no collection specified, missing 'c' parameter")
	}

	if !safe(c) {
		return 403, errors.New("FORBIDDEN attempt to access "+c)
	}

	if cb != "" {
		w.Header().Set("Content-type", "text/javascript")
	}

	C := mongo.DB().C(c)

	var query bson.M

	if q != "" {
		json.Unmarshal([]byte(q), &query)
	} else if id != "" {
		query = bson.M{"_id": bson.ObjectIdHex(id)}
	}

	log.Println("query: ", q, query)

	if padding != "" {
		io.WriteString(w, padding + "(")
	}

	if cb == "" && id == "" {
		io.WriteString(w, "[")
	}

	var result bson.M

	first := true

	err := C.Find(query).For(&result, func() error {

		// hack:
		result["_id"] = result["_id"].(bson.ObjectId).Hex()

		txt, err := json.Marshal(&result)

		if err != nil {
			log.Println(result)
			log.Println("could not marshal bson into json: " + err.Error())
			return err
		}

		log.Println("response object: ", result, string(txt))

		if cb != "" {
			log.Println("padding with callback: ", cb)
			io.WriteString(w, cb+"(")
			w.Write(txt)
			io.WriteString(w, ");\n")
		} else {
			if !first {
				io.WriteString(w, ", ")
			}
			w.Write(txt)
		}

		first = false

		return nil
	})

	if err != nil {
		return 500, err
	}

	if first {
		log.Println("no results found")
	}

	if cb == "" && id == "" {
		io.WriteString(w, "]")
	}

	if padding != "" {
		io.WriteString(w, ");")
	}
	
	return 200, nil
}

func dbPOSTHandler(w http.ResponseWriter, req *http.Request) (int, error) {

	c := req.FormValue("c")

	if c == "" {
		return 400, errors.New("no collection specified, missing 'c' parameter")
	}

	doc, err := ioutil.ReadAll(req.Body)

	if err != nil {
		log.Println("failed to read body of POST request: " + err.Error())
		return 500, err
	}

	var save bson.M

	err2 := json.Unmarshal(doc, &save)

	if err2 != nil {
		log.Println(string(doc))
		log.Println("failed to unmarshal body of POST request: " + err2.Error())
		return 400, err2
	}

	id := bson.NewObjectId()
	save["_id"] = id

	C := mongo.DB().C(c)

	err3 := C.Insert(save)

	if err3 != nil {
		log.Println(save)
		log.Println("failed to insert POST document: " + err3.Error())
		return 400, err3
	}

	uri := "/db?c=" + c + "&id=" + id.Hex()
	http.Redirect(w, req, uri, 302)
	io.WriteString(w, id.Hex())

	err4 := httpd.Publish(c, uri)

	if err4 != nil {
		return 500, err4
	}

	return 200, nil
}

func dbPUTHandler(w http.ResponseWriter, req *http.Request) (int, error) {
	c := req.FormValue("c")
	id := req.FormValue("id")
	if id == "" {
		return 400, errors.New("no id specified, missing 'id' parameter")
	}

	if c == "" {
		return 400, errors.New("no collection specified, missing 'c' parameter")
	}

	doc, err := ioutil.ReadAll(req.Body)

	if err != nil {
		return 500, err
	}

	var save bson.M

	err2 := json.Unmarshal(doc, &save)

	if err2 != nil {
		return 400, err2
	}

	save["_id"] = id

	C := mongo.DB().C(c)

	err3 := C.Insert(save)

	if err3 != nil {
		log.Println(save)
		return 400, err3
	}

	uri := "/db?c=" + c + "&id=" + id
	http.Redirect(w, req, uri, 302)
	io.WriteString(w, id)

	httpd.Publish(c, uri)

	return 200, nil
}

func dbDELETEHandler(w http.ResponseWriter, req *http.Request) (int, error) {
	return 500, rest.INVALID
}

func Handler(w http.ResponseWriter, req *http.Request) (int, error) {

	switch req.Method {
	case "POST":
		return dbPOSTHandler(w, req)
	case "PUT":
		return dbPUTHandler(w, req)
	case "DELETE":
		return dbDELETEHandler(w, req)
	case "GET":
		return dbGETHandler(w, req)
	default:
		return 500, rest.INVALID
	}

	return 200, nil
}
